package org.mmtk.plan.scale.metrix;

import org.mmtk.plan.scale.utils.ArayHelper;
import org.mmtk.utility.Log;
import org.mmtk.utility.options.Options;
import org.vmmagic.pragma.Inline;
import org.vmmagic.pragma.Uninterruptible;

@Uninterruptible 
public class DepthClosureCounts extends ClosureCounts { 
	//to collect per object metric
	private long[] lookFor;
	private long[] acesses;
	private long[] stalls;
	private long[] util;
	private long[] speedup;
	private long[] stallp;
	private long[] stalld;
		
	//depth counters
	private int depth;
	private long totalCount;
	private long minCount;
	private long maxCount;
	@Inline
	private void initDepthCounters() {
		this.depth = 0;
		this.minCount = 0;
		this.maxCount = 0;
		this.totalCount = 0;
	}
	
	@Override
	public void dumpObjectCounters(String message, boolean full) {
		super.dumpObjectCounters(message, full);
		dumpDepthCounters();
	}
	
	@Inline
	private void dumpDepthCounters() {
		int depths = getDepth();
		if(depths == 0) {
			if(Options.scaleDebug.getValue() > 5) {
				Log.writeln("Depth is 0");	
			}
		} else {
			Log.write("DepthMeasure dump: Depth ");		
			Log.write(depths);
			Log.write(" Total ");
			Log.write(getTotalCount());
			Log.write(" Min ");
			Log.write(getMinCount());
			Log.write(" Max ");
			Log.write(getMaxCount());
			Log.write(" Avg ");
			Log.writeln(getTotalCount()/depths);
		}
	}	
	
	public DepthClosureCounts(String name) {
		super(name);
		lookFor = new long[POINTS];
		acesses = new long[POINTS];
		stalls = new long[POINTS];
		util = new long[POINTS];
		speedup = new long[POINTS];
		stallp = new long[POINTS];
		stalld = new long[POINTS];
	}
	
	@Inline
	public void init() {
		super.init();
		this.initDepthCounters();
		this.totalAdded = 0;
		ArayHelper.zeroArray(lookFor);
		ArayHelper.zeroArray(acesses);
		ArayHelper.zeroArray(stalls);
		ArayHelper.zeroArray(util);
		ArayHelper.zeroArray(speedup);
		ArayHelper.zeroArray(stallp);
		ArayHelper.zeroArray(stalld);
	}
	
	public void endClosure() {		
		super.endClosure();
		int processors;
		long workDone = msPushed;
		Log.write("EndClosure, msPushed=");
		Log.write(msPushed);
		Log.write(", msPopped=");
		Log.write(msPopped);
		Log.write(", workDone=");
		Log.write(workDone);
		Log.write(", totalAdded=");
		Log.writeln(totalAdded);
		for(int i = 0; i < POINTS; i++) {
			processors = procs[i];
			if(this.acesses[i] != 0) {
				this.util[i] = (workDone * 100)/(this.acesses[i]);
				this.speedup[i] = (workDone * processors)/(this.acesses[i]);
				this.stallp[i] = (this.stalls[i] * 100)/(this.acesses[i]);
			}
			//if (Options.scaleDebug.getValue() > 1) {
				Log.write(i);
				Log.write(" processors=");
				Log.write(processors);				
				Log.write(" acesses=");
				Log.write(acesses[i]);				
				Log.write(" stalls=");
				Log.write(stalls[i]);
				Log.write(" util=");
				Log.write(util[i]);
				Log.write(" speedup=");
				Log.write(speedup[i]);
				Log.write(" stallp=");
				Log.write(stallp[i]);
				Log.write(" ddepth=");
				Log.write(this.msPopped/processors);
				Log.write(" stalld=");
				Log.writeln(stalld[i]);
			//}
		}
		dumpDepthCounters();
	}
	
	public long getMsPushed() {
		return this.msPushed;
	}

	public void addMsPushed() {
		this.msPushed++;
	}

	public long getMsPopped() {
		return this.msPopped;
	}

	/*
	 * Objects is popped from the stack
	 * We need to note how much work is available at the moment
	 */
	public void addMsPopped() {
		long work = this.msPushed - this.msPopped;
		long remain;
		int processors;
		for(int i = 0; i < POINTS; i++) {
			processors = procs[i];
			if(this.msPopped == this.lookFor[i]) {
				if(work < processors) {
					remain = 0;
					this.acesses[i] += processors;
					this.stalls[i] += processors - work;
					if(this.stalld[i] == 0) {
						this.stalld[i] = this.depth;
					}	
				} else {
					remain = work%processors;
					this.acesses[i] += work - remain;
				}
				this.lookFor[i] = this.msPushed - remain;
				
				if (Options.scaleDebug.getValue() > 3) {
					Log.write("addMsPopped processors=");
					Log.write(processors);
					Log.write(" msPushed=");
					Log.write(msPushed);
					Log.write(" msPopped=");
					Log.write(msPopped);
					Log.write(" work=");
					Log.write(work);
					Log.write(" remain=");
					Log.write(remain);
					Log.write(" lookFor=");
					Log.write(lookFor[i]);
					Log.write(" acesses=");
					Log.write(acesses[i]);
					Log.write(" stalls=");
					Log.writeln(stalls[i]);
				}
			}
		}	
		super.addMsPopped();
	}
	
	public long getNarrow() {
		return this.narrow;
	}

	public void addNarrow() {
		this.narrow++;
	}
	
	public void addDepth(long objects) {
		this.depth++;
		this.totalCount += objects;
		if(objects > this.maxCount || this.maxCount == 0) {
			this.maxCount = objects;
		}
		if(objects < this.minCount || this.minCount == 0) {
			this.minCount = objects;
		}
	}
	
	public int getDepth() {
		return this.depth;
	}
	
	public long getTotalCount() {
		return this.totalCount;
	}
	
	public long getMinCount() {
		return this.minCount;
	}
	
	public long getMaxCount() {
		return this.maxCount;
	}
	
	long getFirstStallDepth() {
		long result;
		long first = 0;
		long avg;
		long sum = 0;
		int nonNull = 0;
		for(int i = 0; i < POINTS; i++) {
			if(this.stalld[i] != 0) {
				nonNull++;
				sum += this.stalld[i];
				if(first == 0) {
					first = this.stalld[i];
				}
			}
		}
		if(nonNull != 0) {
			avg = sum/nonNull;
			result = (first < avg) ? first : avg;
		} else {
			result = this.pushed/1024;
		}
		return result;
	}
	
	private long totalAdded;
	public void addWork(long objects) {
		if (Options.scaleDebug.getValue() > 3) {
			Log.write("addWork objects=");
			Log.write(objects);
			Log.write(" msPushed=");
			Log.write(msPushed);
			Log.write(" msPopped=");
			Log.writeln(msPopped);
		}
		totalAdded += objects;
		long work;
		long remain;
		int processors;
		for (int i = 0; i < POINTS; i++) {
			processors = procs[i];
			if (Options.scaleDebug.getValue() > 3) {
				Log.write("addWorkprocessors=");
				Log.write(processors);
				Log.write(" lookFor=");
				Log.write(lookFor[i]);
				Log.write(" acesses=");
				Log.write(acesses[i]);
				Log.write(" stalls=");
				Log.writeln(stalls[i]);
			}
			if (this.msPopped + objects < this.lookFor[i]) {
				this.acesses[i] += objects;
				this.lookFor[i] += objects;
				continue;
			}
			work = (this.msPushed + objects) - this.lookFor[i];
			if (work < processors) {
				remain = 0;
				this.acesses[i] += processors;
				this.stalls[i] += processors - work;
				if (this.stalld[i] == 0) {
					this.stalld[i] = this.depth;
				}
			} else {
				remain = work % processors;
				this.acesses[i] += work - remain;
			}
			this.lookFor[i] = this.msPushed + objects - remain;
			if (Options.scaleDebug.getValue() > 3) {
				Log.write("addWorkprocessors=");
				Log.write(processors);
				Log.write(" work=");
				Log.write(work);
				Log.write(" remain=");
				Log.write(remain);
				Log.write(" lookFor=");
				Log.write(lookFor[i]);
				Log.write(" acesses=");
				Log.write(acesses[i]);
				Log.write(" stalls=");
				Log.writeln(stalls[i]);
			}
		}

		this.msPushed += objects;
		this.msPopped += objects;
	}
}
